Skip to content

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Jan 26, 2018

Implemented "exstensible fixed statements"

The feature expands support of fixed to types that provide a special ref-returning method as described in https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md
(currently only arrays and strings can be used directly in fixed)

NOTE: the name of the method is likely to change. It will be updated in subsequent changes.

@VSadov VSadov requested a review from a team as a code owner January 26, 2018 06:51
@VSadov VSadov changed the title Custom fixed [WIP] Custom fixed Feb 8, 2018
@VSadov VSadov added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Feb 8, 2018
@VSadov VSadov requested review from a team as code owners February 8, 2018 21:53
@VSadov VSadov changed the title [WIP] Custom fixed Extensible Fixed Statement Feb 8, 2018
@VSadov VSadov removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Feb 8, 2018
@VSadov
Copy link
Member Author

VSadov commented Feb 9, 2018

@dotnet/roslyn-compiler - please review. This is a 7.3 feature PR to a feature branch.

REM Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

REM Run the node generator to create new bound tree node definitions.
REM You must run this in the directory containing Generated.cs.
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove this file.
I've been using build\scripts\generate-compiler-code.cmd. That one updates all the generated files at once. #Closed

return true;
}

private MethodSymbol GetDangerousGetPinnableReferenceMethod(BoundExpression initializer, DiagnosticBag additionalDiagnostics)
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: no need for "Dangerous" here #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are not keeping "Dangerous" in the name. Once I implement the name change this will be renamed too


In reply to: 167647561 [](ancestors = 167647561)

elementType = ((BoundAddressOfOperator)initializerOpt).Operand.Type;
getPinnableMethod = GetDangerousGetPinnableReferenceMethod(initializerOpt, additionalDiagnostics);

bool extensisbleFixedEnabled = ((CSharpParseOptions)initializerOpt.SyntaxTree.Options)?.IsFeatureEnabled(MessageID.IDS_FeatureExtensibleFixedStatement) != false;
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in "extensible" #Closed

// check for String
// NOTE: We will allow DangerousGetPinnableReferenceMethod to take precendence, but only if it is a member of System.String
if (initializerType.SpecialType == SpecialType.System_String &&
((object)getPinnableMethod == null || !extensisbleFixedEnabled || getPinnableMethod.ContainingType.SpecialType != SpecialType.System_String))
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extensisbleFixedEnabled [](start = 67, length = 23)

I don't think the presence/absence of the feature flag should affect binding. Only diagnostics should be conditional on the feature flag.
We could probably remove extensisbleFixedEnabled entirely and rely on CheckFeatureAvailability below. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very special case. We want that extensible pinnable to win over when implemented on System.String.


In reply to: 167648804 [](ancestors = 167648804)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You a right though - we should not check extensisbleFixedEnabled here.
The implementors of System.String will guarantee backwards compatibility of the new pattern. So this is more a matter of runtime versioning, not compiler versioning.


In reply to: 169196056 [](ancestors = 169196056,167648804)

if (extensisbleFixedEnabled)
{
// not a specially known type, check for getPinnableMethod
if (getPinnableMethod != null)
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPinnableMethod [](start = 32, length = 17)

cast to object? #Closed

break;
}
else
else if (additionalDiagnostics != null)
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like additionalDiagnostics is always set. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not always. it is reported only on cases when we found the member, but something was wrong with it.
The idea is to help implementers of the method, but not complain about the method when it is clearly not provided.


In reply to: 167649336 [](ancestors = 167649336)

// convert to generate any additional conversion errors
initializerOpt = GenerateConversionForAssignment(declType, initializerOpt, diagnostics);
return true;
// did not find anythig callable
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: anything #Closed

);
}

#endregion Custom fixed statement tests
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#endregion Custom fixed statement tests [](start = 7, length = 40)

Can we test the following?

  • fixed (int* p = (dynamic)new Fixable())
  • GetPinnable that returns ref readonly
  • check semantic model (although it looks like the selected GetPinnable method will not be exposed)
  • test with ambiguous GetPinnable method (maybe one instance method and one extension, or maybe two extensions) #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed is currently not exposed in semantic model at all, so there is no way to test the differences.
For other suggestions more tests were added.


In reply to: 167650001 [](ancestors = 167650001)

<value>The given expression cannot be used in a fixed statement</value>
</data>
<data name="ERR_FixableHelperDoesNotFitThePattern" xml:space="preserve">
<value>The method '{0}' does not meet requirements necessary </value>
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method '{0}' does not meet requirements necessary [](start = 11, length = 53)

I feel both messages should give more information about what is expected. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logged an issue to follow up with more detailed diagnostics - #24947


In reply to: 167650429 [](ancestors = 167650429)

else

var analyzedArguments = AnalyzedArguments.GetInstance();
BoundExpression getPinnableReferenceCall = BindMethodGroupInvocation(initializer.Syntax, initializer.Syntax, methodName, (BoundMethodGroup)boundAccess, analyzedArguments, bindingDiagnostics, queryClause: null, allowUnexpandedForm: false);
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: please break those extra-long lines #Closed

DiagnosticBag bindingDiagnostics = DiagnosticBag.GetInstance();
try
{
var boundAccess = BindInstanceMemberAccess(initializer.Syntax, initializer.Syntax, initializer, methodName, rightArity:0, typeArgumentsSyntax: default, typeArguments: default, invoked:true, bindingDiagnostics);
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invoked:true [](start = 192, length = 12)

Nit: formatting on invoked:true and some other arguments #Closed


return true;
var getPinnableReferenceMethod = call.Method;
if (getPinnableReferenceMethod is ErrorMethodSymbol ||
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid testing a specific type? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how we test for error method symbol in other places.


In reply to: 167652407 [](ancestors = 167652407)

// (6,25): error CS8320: Feature 'extensible fixed statement' is not available in C# 7.2. Please use language version 7.3 or greater.
// fixed (int* p = new Fixable())
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "new Fixable()").WithArguments("extensible fixed statement", "7.3").WithLocation(6, 25),
// (6,25): error CS9365: The given expression cannot be used in a fixed statement
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CS9365 [](start = 33, length = 6)

I think the only error in this code is the LangVer related one. #Closed

if (ReferenceEquals(initializerOpt, null))
if (initializerOpt?.HasAnyErrors != false)
{
return false;
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a test cover this path? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test


In reply to: 167654777 [](ancestors = 167654777)


// Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd)

ERR_ExprCannotBeFixed = 9365,
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please insert the error codes above the comment #Closed

/// fixed(int* ptr = &amp;v){ ... } == becomes ===>
///
/// pinned ref int pinnedTemp = ref v; // pinning managed ref
/// int* ptr = (int*)&amp;pinnedTemp; // unsafe cast to unmanaged ptr
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use CDATA to avoid escaping ampersand #Resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the CDATA, "&" can be changed to "&" on the first line.


In reply to: 167655416 [](ancestors = 167655416)


// .GetPinnable()
var getPinnableCall = getPinnableMethod.IsStatic?
factory.Call(null, getPinnableMethod, callReceiver):
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: different indentation than the rest of the code #Closed

}

// .GetPinnable()
var getPinnableCall = getPinnableMethod.IsStatic?
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meaning here is really to distinguish regular methods from extension methods, is that right? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but we are already in lowering, so we should emit an ordinary static call.


In reply to: 167659923 [](ancestors = 167659923)

}
";

var compVerifier = CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, verify: Verification.Fails, expectedOutput: @"5456");
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify: Verification.Fails [](start = 93, length = 26)

Would this be verifiable by some updated verification rules? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ref-returning method will be, but any use of int * will not


In reply to: 167672486 [](ancestors = 167672486)

Error(diagnostics, ErrorCode.ERR_ManagedAddr, initializerSyntax, elementType);
hasErrors = true;
// See ExpressionBinder::BindPtrToArray (though most of that functionality is now in LocalRewriter).
var arrayType = (ArrayTypeSymbol)initializerType;
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider inlining, since used once #Closed

VariableDeclaratorSyntax declarator = fixedInitializer.Syntax.FirstAncestorOrSelf<VariableDeclaratorSyntax>();
Debug.Assert(declarator != null);

pinnedTemp = factory.SynthesizedLocal(
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// pinned ref int pinnedTemp #Closed

getPinnableCall,
isRef: true);

// &pinnedTemp;
Copy link
Member

@jcouv jcouv Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No semi-colon #Resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: doesn't need a semi-colon: // &pinnedTemp


In reply to: 167677135 [](ancestors = 167677135)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with review pass (iteration 8)

SyntaxNode initializerSyntax = initializerOpt.Syntax;

if (ReferenceEquals(initializerType, null))
if ((object)initializerType == null)
Copy link
Member

@agocke agocke Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe is null now? #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== null is used throughout the file. I think we might want to fix that globally, but one change would just look odd.


In reply to: 167688236 [](ancestors = 167688236)

{
if (initializer.Type.SpecialType == SpecialType.System_Void)
{
return null;
Copy link
Member

@agocke agocke Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method should be suffixed with "Opt" if it can return null. #Resolved

((object)getPinnableMethod == null || !extensisbleFixedEnabled || getPinnableMethod.ContainingType.SpecialType != SpecialType.System_String))
{
elementType = this.GetSpecialType(SpecialType.System_Char, diagnostics, initializerSyntax);
Debug.Assert(!elementType.IsManagedType);
Copy link
Member

@agocke agocke Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this assert is supposed to protect against. If the user code is wrong, why would we assert instead of provide an error? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless assert. it will always pass. removed.


In reply to: 167708843 [](ancestors = 167708843)


Error(diagnostics, ErrorCode.ERR_ExprCannotBeFixed, initializerSyntax);
}
finally
Copy link
Member

@agocke agocke Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finally seems overly complicated. Couldn't we just stick the Error call in an else and always call this at the end of the if/else tree? That would remove all the break statements as well. #Resolved

Diagnostic(ErrorCode.ERR_FixedNotNeeded, "&i"),
// (14,26): error CS0213: You cannot use the fixed statement to take the address of an already fixed expression
Diagnostic(ErrorCode.ERR_FixedNotNeeded, "&i").WithLocation(7, 23),
// (14,26): error CS9365: The given expression cannot be used in a fixed statement
Copy link
Member

@agocke agocke Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message seems worse (less specific) than the previous one. Why did it change? #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old message was actually incorrect. "already fixed" would apply if & was used. Here we do not use & . It might actually work if there is an extension method of right shape., but most likely it is just a typo.

I do not think the new message is worse than the old one.


In reply to: 167710934 [](ancestors = 167710934)

";
CreateStandardCompilation(text, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics(
// (20,23): error CS0254: The right hand side of a fixed statement assignment may not be a cast expression
// (20,23): error CS9365: The given expression cannot be used in a fixed statement
Copy link
Member

@agocke agocke Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. #ByDesign

Copy link
Member Author

@VSadov VSadov Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the old message was pretty random here. It only looks like it is more appropriate. The expression that we try to fix makes no sense in many ways.
I think this is the case where user of fixed should read documentation or check web for examples. It is not a lot to ask when unsafe features are involved.


In reply to: 167711048 [](ancestors = 167711048)

}
else
{
// if feature was enabled but somethng went wrong with the method, report that
Copy link
Member

@jcouv jcouv Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems out-of-date
Never mind #Closed

}
else
{
// if feature was enabled but somethng went wrong with the method, report that
Copy link
Member

@jcouv jcouv Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: something #Resolved

}
else
{
// if feature was enabled but somethng went wrong with the method, report that
Copy link
Member

@jcouv jcouv Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should report that the method was wrong regardless of the feature being enabled.
The errors should provide you a path (instructions) to enabling the feature and putting an appropriate method in place. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree on that.
The extra errors are primarily for those who develop the method, and they should have the feature enabled in the first place.
For others it would be just an annoyance - "please bump up the version to enable the feature, that you still cannot use and will only get more errors since the method that you very likely do not own or can change is broken"

I think if we have both cases - 1) feature is not enabled and 2) feature is unusable, we should not give extra diagnostics since that would not be actionable.


In reply to: 169487046 [](ancestors = 169487046)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I did not consider that the diagnostics that we could not find a helper method is interesting at all. It was very noisy.
We would get those errors anywhere we try to pin something that we cannot pin (since nearly everything now can be pinned via an extension method, if such available).

Then I figured that there is one situation when you want those errors - when you are designing the actual helper, so I added some heuristics to detect that.
My criteria are - the feature is enabled and the method is "almost there, but not quite right".
Then I believe that someone is developing the helper and would want to see the extra diagnostics.


In reply to: 169494680 [](ancestors = 169494680,169487046)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. That works

}

// check for a special ref-returning method
var additionalDiagnostics = DiagnosticBag.GetInstance();
Copy link
Member

@jcouv jcouv Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the motivation for removing the try/catch for freeing this instance? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Andy's comment on this PR. I was undecided which way is better, so if someone also thinks that try/finally makes this more complex than needed, I've removed it


In reply to: 169487211 [](ancestors = 169487211)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with review pass (iteration 9)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@VSadov
Copy link
Member Author

VSadov commented Feb 21, 2018

@dotnet/roslyn-compiler - PING. One more review is needed.

Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@VSadov
Copy link
Member Author

VSadov commented Feb 22, 2018

Thanks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants